#include <windows.h>
#include <mmsystem.h>
#include <krnlTypes.h>
#include "sysSettings.h"

///////////////////////////////////////////////////////////////////////////////
// Modele global variables
static HWAVEOUT	l_wave_out_handle;						// Handle to the WAVE In Device
static HANDLE		l_wave_out_thread;						// Handle of wave input 
static HANDLE		l_wave_out_stop_event;				// thread stop event handler
static HANDLE		l_wave_out_event;						// Handle of wave input handle
static BOOL			l_cleanup_installed = FALSE;	// true if cleanup handler is installed
static WAVEHDR	l_wave_out_header1[2];				// Wave in header
static dosByte	l_wave_out_buffer1[aiWAVEIN_BUFFER_SIZE*2];	// wave in buffer 1
static dosByte	l_wave_out_buffer2[aiWAVEIN_BUFFER_SIZE*2];	// wave in buffer 1
static dosByte	l_buffer_index;

///////////////////////////////////////////////////////////////////////////////
// Function prototypes
static DWORD WINAPI waveOutProc(LPVOID arg);
static void drvAOCleanUp(void);

///////////////////////////////////////////////////////////////////////////////
// Initializes Audio Input device
void drvAOInit(void)
{
	// init variables
	l_wave_out_stop_event = NULL;

  // install exit handler
  if( !l_cleanup_installed )
  {
    atexit(drvAICleanUp);
    l_cleanup_installed = TRUE;
  }
}

///////////////////////////////////////////////////////////////////////////////
// Starts audio input
void drvAOStart(void)
{
  // create thread stop event
  l_wave_out_stop_event = CreateEvent( NULL, TRUE, FALSE, NULL);

  // create thread running event
  l_wave_out_event = CreateEvent( NULL, TRUE, FALSE, NULL);
	
	// create wave in thread
  l_wave_out_thread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)waveOutProc, NULL, 0, NULL);
}

///////////////////////////////////////////////////////////////////////////////
// Stops audio input
void drvAOStop(void)
{
	if( l_wave_out_stop_event != NULL )
		SetEvent(l_wave_out_stop_event);
}

///////////////////////////////////////////////////////////////////////////////
// Cleans up audio input
void drvAOCleanUp(void)
{
  // stop threads
  SetEvent(l_wave_out_stop_event );
  WaitForSingleObject( l_wave_out_thread, 1000);
}

///////////////////////////////////////////////////////////////////////////////
// Wave-in handler thread
static DWORD WINAPI waveOutProc(LPVOID arg)
{
  DWORD wait_res;
  HANDLE handles[2];
	MMRESULT err = 0;
	WAVEFORMATEX waveFormat;

	l_buffer_index = 0;
	
	// Initialize the WAVEFORMATEX 
	waveFormat.wFormatTag = WAVE_FORMAT_PCM;
	waveFormat.nChannels = 1;
	waveFormat.nSamplesPerSec = aiSAMPLE_RATE;
	waveFormat.wBitsPerSample = 16;
	waveFormat.nBlockAlign = waveFormat.nChannels * (waveFormat.wBitsPerSample/8);
	waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
	waveFormat.cbSize = 0;

	// open  wave input
	l_wave_out_handle = NULL;
	err = waveInOpen(&l_wave_out_handle, WAVE_MAPPER, &waveFormat, (DWORD_PTR)l_wave_out_event, 0, CALLBACK_EVENT);

	// prepare buffers
	ZeroMemory(&l_wave_out_header[0], sizeof(WAVEHDR) * 2);

	l_wave_out_header[0].dwBufferLength = sizeof(l_wave_out_buffer1);
	l_wave_out_header[0].lpData = (LPSTR)l_wave_out_buffer1;

	l_wave_out_header[1].dwBufferLength = sizeof(l_wave_out_buffer1);
	l_wave_out_header[1].lpData = (LPSTR)l_wave_out_buffer1;

	err = waveInPrepareHeader(l_wave_out_handle, &l_wave_out_header[0], sizeof(WAVEHDR));
	err = waveInPrepareHeader(l_wave_out_handle, &l_wave_out_header[1], sizeof(WAVEHDR));

	err = waveInAddBuffer(l_wave_out_handle, &l_wave_out_header[0], sizeof(WAVEHDR));
	err = waveInAddBuffer(l_wave_out_handle, &l_wave_out_header[1], sizeof(WAVEHDR));

  // store event handles
  handles[0] = l_wave_out_stop_event;
  handles[1] = l_wave_out_event;

  // set running event
  ResetEvent( l_wave_out_stop_event );

	// starts wave in device
	err = waveInStart(l_wave_out_handle);

  // wait for either thread stop or communication event
	while( TRUE )
	{
		// wait for event
		wait_res = WaitForMultipleObjects(2, handles, FALSE, INFINITE);

		// check if stop requested
		if( wait_res == WAIT_OBJECT_0 )
		{
			waveInReset(l_wave_out_handle);
			break;
		}
		else
		{
			ResetEvent(l_wave_out_event);

			aiProcessSample(l_wave_out_header[l_buffer_index].lpData);

			waveInAddBuffer(l_wave_out_handle, &l_wave_out_header[l_buffer_index], sizeof(WAVEHDR));

			l_buffer_index = 1 - l_buffer_index;
		}

    // Yield to other task
    Sleep(0);
  }

	// close wave in device
	if( l_wave_out_handle != NULL )
		waveInClose(l_wave_out_handle);

  // exit thread
  ExitThread( 0 );
}
